
using System;
using MvvmFx.Common.ViewModels.Behaviors.Observable;

namespace MvvmFx.Common.ViewModels
{
    /// <summary>
    /// The SelectableCollection class inherits from <see cref="ViewModelCollection"/>
    /// and stores a collection of <see cref="SelectableViewModel"/> items and tracks
    /// which item is currently selected.
    /// </summary>
    /// <example>
    /// The following example shows how to expose a SelectableCollection as a property of
    /// a ViewModel class.
    /// <code>
    ///  public class TimelineViewModel : ViewModelBase
    ///{
    ///    private readonly SelectableCollection<TimePeriodViewModel> timePeriods;
    ///    
    ///    public TimelineViewModel()
    ///    {
    ///        timePeriods = new SelectableCollection<TimePeriodViewModel>();
    ///    }
    ///    public SelectableCollection<TimePeriodViewModel> TimePeriods
    ///    {
    ///        get { return timePeriods; }
    ///    }
    ///}
    /// </code>
    /// </example>
    /// The following example shows some ways to use a SelectableCollection property of
    /// a ViewModel class in code.
    /// <code>
    /// 
    /// // Set a selected item
    /// timePeriod.IsSelected = true;
    /// 
    /// // Check if item is selected
    /// if(!viewModel.TimePeriods.HasSelectedItem)
    /// {
    ///     // Set the selected item
    ///     viewModel.TimePeriods[0].IsSelected = true;
    /// 
    ///      // Set the selected item direclty
    ///      viewModel.TimePeriods.SelectedItem = timePeriod;
    /// 
    ///     // Get the selected item
    ///     var selected = viewModel.TimePeriods.SelectedItem;
    /// }
    /// </code>
    /// </example>
    /// <example>
    /// The following example shows how to databind to a SelectableCollection
    /// in XAML. Bind the ItemsSource property of an ItemsControl to the collection 
    /// itself, then bind the SelectedValue property to the SelectedItem property
    /// of SelectableCollection. Make this binding two way, and the SelectableCollection
    /// will automatically track the selected item in the control.
    /// <code>
    ///    <s:SurfaceListBox 
    ///         ItemsSource="{Binding TimePeriods}"
    ///         ItemTemplate="{StaticResource timePeriodItemTemplate}"
    ///         SelectedValue="{Binding Path=TimePeriods.SelectedItem, Mode=TwoWay}"
    ///         IsSynchronizedWithCurrentItem="True" />
    /// </code>
    /// </example>
    /// <typeparam name="T">The type&lt;T&gt; of selectable collection restricted to base <see cref="SelectableViewModel"/> class.</typeparam>
    public class SelectableCollection<T> : ObservableViewModelCollection<T> where T : SelectableViewModel
    {
        #region Private Members

        /// <summary>
        /// The selected item&lt;T&gt; field.
        /// </summary>
        private T _selectedItem;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectableCollection{T}"/> class.
        /// </summary>
        public SelectableCollection()
            : base()
        {
            this.AddItemHandler("IsSelected", OnItemSelectedChanged);
        }

        #endregion

        #region Events

        /// <summary>
        /// The SelectedItemChanged&lt;T&gt; event.
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> SelectedItemChanged;

        #endregion

        #region IsSelected Item

        /// <summary>
        /// Gets or sets the value of the <see cref="SelectedItem"/> property.
        /// </summary>
        public T SelectedItem
        {
            get
            {
                return _selectedItem;
            }

            set
            {
                if (value != _selectedItem)
                {
                    if (_selectedItem != null)
                    {
                        _selectedItem.IsSelected = false;
                    }

                    if (value != null && !value.IsSelected)
                    {
                        value.IsSelected = true;
                    }

                    _selectedItem = value;
                    OnSelectedItemChanged(new ItemEventArgs<T>(value));
                }
            }
        }

        /// <summary>
        /// When the SelectedItem property changes, raise the SelectedItemChanged
        /// event and refresh the HasSelectedItem property to bindings.
        /// </summary>
        /// <param name="e">The argument containg the selected item.</param>
        protected void OnSelectedItemChanged(ItemEventArgs<T> e)
        {
            OnPropertyChanged("SelectedItem");

            var handler = SelectedItemChanged;
            if (handler != null)
                handler(this, e);

            OnPropertyChanged("HasSelectedItem");
        }

        /// <summary>
        /// Gets whether the collection has a selected item
        /// </summary>
        public bool HasSelectedItem
        {
            get
            {
                return _selectedItem != null;
            }
        }

        #endregion

        #region Collection/Item Property changed

        /// <summary>
        /// Occurs when the <see cref="OnSelectedItemChanged"/> changes. 
        /// </summary>
        protected virtual void OnItemSelectedChanged(object sender, ItemPropertyChangedEventArgs<T> e)
        {
            if (e.Item.IsSelected)
            {
                this.SelectedItem = e.Item;
            }
            else
            {
                if (this.SelectedItem == e.Item)
                {
                    this.SelectedItem = null;
                }
            }
        }

        /// <summary>
        /// If an item is removed and is the selected item, set the selected item to null
        /// </summary>
        /// <param name="e"></param>
        protected override void OnItemRemoved(ItemEventArgs<T> e)
        {
            if (e.Item == _selectedItem)
            {
                this.SelectedItem = default(T);
            }

            base.OnItemRemoved(e);
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Override ClearItmes to clear the selected item when the collection is cleared.
        /// </summary>
        protected override void ClearItems()
        {
            // Set the selected item to the default value... null.
            this.SelectedItem = default(T);

            base.ClearItems();
        }

        #endregion
    }
}